﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace DiscreteMath
{
	[JsonConverter(typeof(ConfigurationConverter))]
    /// <summary>
    /// Represents a configuration in a finite state machine - a tuple used to define transitions in the machine.
    /// </summary>
	public struct Configuration : IEquatable<Configuration>
	{
        /// <summary>
        /// Initializes a new <see cref="Configuration"/> using the specified state and letter.
        /// </summary>
        /// <param name="state">The state of the configuration.</param>
        /// <param name="letter">The letter of the configuration</param>
		public Configuration(State state, char letter) : this()
		{
			this.State = state;
			this.Letter = letter;
		}

        /// <summary>
        /// Gets the state of the configuration.
        /// </summary>
        public State State { get; private set; }

        /// <summary>
        /// Gets the letter of the configuration.
        /// </summary>
        public char Letter { get; private set; }


        public static bool operator ==(Configuration first, Configuration second)
        {
            return first.Equals(second);
        }

        public static bool operator !=(Configuration first, Configuration second)
        {
            return !first.Equals(second);
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
		public override string ToString()
		{
			return string.Format(CultureInfo.InvariantCulture, "State: {0}, Letter: {1}", this.State, this.Letter);
		}

        /// <summary>
        /// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
		public override bool Equals(object obj)
		{
            if (!(obj is Configuration))
            {
                return false;
            }

			return this.Equals((Configuration)obj);
		}

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
		public override int GetHashCode()
        {
            // Use the algorithm given in Josh Bloch's fabulous Effective Java
            unchecked
            {
                int hash = 17 * 23 + this.State.GetHashCode();
                hash = hash * 23 + this.Letter.GetHashCode();
                return hash;
            }
		}

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
        /// </returns>
		public bool Equals(Configuration other)
		{
			return this.State.Equals(other.State) && this.Letter.Equals(other.Letter);
		}

		/// <summary>
		/// Used for parsing text into configurations.
		/// </summary>
		static string[] delimiters = { ", Letter:" };
		/// <summary>
		/// Parses a text into a <see cref="Configuration"/>. This is used by the JSON Serializer to convert from a json dictionary back to a 
		/// <see cref="TransitionDictionary"/> since the keys there are configurations. It relies on the fact, that configurations are serialized by calling ToString
		/// </summary>
		/// <param name="text">The text to create the configuration from.</param>
		/// <returns>A new configuration created by parsing the text.</returns>
		public static explicit operator Configuration (string text)
		{
			string key = text.ToString();

			string[] split = key.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
			string state = split[0].Replace("State:", string.Empty).Trim();
			string letter = split[1].Trim();
			if (letter.Length != 1)
			{
				throw new ArgumentException(string.Format("The letter may only be a single character, instead it is: {0}", letter));
			}
			return new Configuration(new State(state), letter.First());
		}
	}
}
